package ServerClient;


import java.net.*;
import java.io.*;
import java.net.InetAddress;
import java.util.logging.Level;
import java.util.logging.Logger;


/**
 * Class to handle the TCP connection
 * This class runs as a thread. Connection will be server or client depending on
 * the arguments passed to the constructor.
 * 
 * After conections is established the thread then enters a listening
 * state which will lock until an object is recieved. 
 * If a command is recieved no futher data can be recieved 
 * until this command has been read has been read.
 * 
 * sendCmd() is used by other threads to send commnands to the oher peer. 
 */
/**
 * Class to handle the TCP connection
 * @author bjorn
 */
public class TcpConnection extends Thread {
    // default values
    private static String ip = "127.0.0.1";
    private static int port = 9002;
    private boolean isServer = false;
    private static boolean connected = false;
    private boolean ready = false;
    private Socket socket;
    private ServerSocket serverSocket;
    private ObjectOutputStream output;
    private ObjectInputStream input;
    private ClientServerCmd rec;

    // client constructor
    public TcpConnection(String ipIn, int portIn) {
        isServer = false;
        this.port = portIn;
        this.ip = ipIn;
        this.start();
    }
    // Server constructor
    public TcpConnection(int portIn) {
        this.port = portIn;
        isServer = true;
        this.start();
    }
    // Gets the connected clients ip
    public InetAddress getClientIp() {
        if (isConnected() && isServer)
        return(socket.getInetAddress());
        return (null);
        
    }
    
    // Am i a server?
    public boolean isServer() {
        return(isServer);
    }
    /* is connection established?
     *
     */
    public boolean isReady() {
        return (ready);
    }
    /* Returns command if command is recieved, null command otherwise
     */

    public ClientServerCmd readCmd() {
        if (isReady()){
            ready = false;
            return rec;
        }
        return new ClientServerCmd(ClientServerCmd.NULL);
    }
    /* Thread main loop. Listening state. 
     */

    @Override
    public void run() {
        if (isServer) {            
            update("Server thread started");
            this.startServer(port);             // Blocks until client connects
        } else {
            update("Client thread started");
            this.startClient(ip, port);
        }
        this.ready = true;
        update("main loop started (debug)");
        while (true) {
            try {
                Thread.sleep(1000);
                if (!ready) {
                    rec = readObject();
                    ready = true;
                }
            } catch (InterruptedException ex) {
                update("thread interupted");
            }
        }
    }
    /* Starts a client connection to the given ip and port
     * Returns true if connection is established, false otherwise
     */

    public boolean startClient(String inIp, int inPort) {
        if (connected) {
            update("Already connected");
        } else {
            try {
                {
                    ip = inIp;
                    port = inPort;
                    socket = new Socket(ip, port);
                    input = new ObjectInputStream(socket.getInputStream());
                    output = new ObjectOutputStream(socket.getOutputStream());
                    update("ClientSocket connected successfully" );
                    connected = true;
                    return true;
                }

            } catch (StreamCorruptedException sc) {
                sc.printStackTrace();
                update("ObjectInputStream corrupted in");
            } catch (BindException b) {
                b.printStackTrace();
                update("Bind Exception");
                update("Address in use");
            } catch (UnknownHostException uh) {
                uh.printStackTrace();
                update("Unknown Host Exception");
            } catch (IOException io) {
                io.printStackTrace();
                update("I/O Exception connecting");
                update("Did you start the server first?");
            }
        }
        return false;
    }
    /* Starts a a server at the given port
     * initiates listening state by callinf run()
     */

    public boolean startServer(int inPort) {
        if (connected) {
            update("Already connected");
        } else {
            try {
                serverSocket = new ServerSocket(inPort);
                update("Waiting for client to connect... (thread blocked)");
                socket = serverSocket.accept();
                // thread blocks until client connects
                output = new ObjectOutputStream(socket.getOutputStream());
                input = new ObjectInputStream(socket.getInputStream());
                connected = true;
                update("Connection to " + getClientIp().getHostAddress() +  " initiated successfully");          
                return true;
            } catch (IOException e) {
                update("I/O Exception in server");
                //update(e);
            }
        }
        return false;
    }
    /* is connection established?
     *
     */

    public boolean isConnected() {
        return (connected);
    }

    /* Sends message to the server
     * 
     */
    public boolean writeCmd(Object object) {
        if (connected) {
            try {
                output.writeObject(object);
                output.flush();
            } catch (IOException io) {
                io.printStackTrace();
                update("I/O Exception sending");
            }
        }
        return false;
    }

    /* Private method to read object from server
     * WARNING! WILL BLOCK THREAD
     */
    private ClientServerCmd readObject() {
        if (isConnected() && !isReady()) {
            try {
                ClientServerCmd read = (ClientServerCmd) input.readObject();
                return read;
            } catch (ClassNotFoundException cnf) {
                cnf.printStackTrace();
                update("Cast Exception reading");
            } catch (IOException io) {
                io.printStackTrace();
                update("I/O Exception reading");
            }
        }
        return null;
    }
    
    /*
     * Unfortunately since the server and client tcp connections are in one
     * class it is unnecessarily complex to send a ServerGUI OR a ClientGUI
     * to this class, so for now we will just have to print to the standard out
     */
    private void update(String s) {
        System.out.println(s);
    }
    
    /* closes connection
     * 
     * 
     */
    public void close() {
        try {
            this.interrupt();
            if(isConnected()) socket.close();
            serverSocket.close();
            connected = false;
        } catch (IOException io) {
            io.printStackTrace();
            update("I/O Exception closing");
        }
    }
}
